﻿/******************************************************
* author :  cwj
* email  :  chenwenji_360@live.com 
* history:  created by cwj 2015/8/3 10:08:49 
* clrversion :4.0.30319.18444
******************************************************/

using Machine.DataAccess.Common.ORM;
using Machine.DataAccess.Linq;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Text;
using Machine.DataAccess.Transaction;

namespace Machine.DataAccess.Operation.Sqlite
{
    class SqliteDataSchemCreator : DataSchemCreatorBase
    {
        //private static Lazy<SqliteDataSchemCreator> _instance = new Lazy<SqliteDataSchemCreator>(() =>
        //    {
        //        return new SqliteDataSchemCreator();
        //    }, true);
        //public static SqliteDataSchemCreator Instance { get { return _instance.Value; } }
        private static object writeLock = new object();

        public SqliteDataSchemCreator(ProviderElement providerElement) : base(providerElement) { }

        protected override string SQL_CreateTable
        {
            get { return string.Format("Create Table {0} ({1} {2} Primary Key NOT NULL AUTOINCREMENT)", TABLEARG_NAME, COLUMARG_NAME, COLUMTYPEARG_NAME); }
        }

        protected override string SQL_CreateBasic
        {
            get { return string.Format("Alter Table {0} Add {1} {2}", TABLEARG_NAME, COLUMARG_NAME, COLUMTYPEARG_NAME); }
        }

        protected override string SQL_CreateRelation
        {
            get { return string.Format("Alter Table {0} Add {1} {2} References {3} ({4}) ON DELETE CASCADE", TABLEARG_NAME, COLUMARG_NAME, COLUMTYPEARG_NAME, OUTTERTABLE_NAME, OUTTERKEY_NAME); }
        }

        public override bool CreateTable(string tableName, string primaryKeyName, string primaryKeyType,bool isAutoKey = false)
        {
            var strTemp = SQL_CreateTable.Replace(TABLEARG_NAME, tableName)
                .Replace(COLUMARG_NAME, primaryKeyName)
                .Replace(COLUMTYPEARG_NAME, primaryKeyType);
            if (!isAutoKey) strTemp = strTemp.Replace("AUTOINCREMENT", "");
            return DataResult.CodeFirstExcuteNonQuery(new TranslateResult(strTemp, new DbParameter[0]),this.ProviderElement);
        }

        public override bool CreateBaseType(Type table, PropertyInfo colum)
        {
            var strTemp = SQL_CreateBasic.Replace(TABLEARG_NAME, table.GetTableName())
                .Replace(COLUMARG_NAME, colum.Name)
                .Replace(COLUMTYPEARG_NAME, this.GetDbType(colum.PropertyType));

            return DataResult.CodeFirstExcuteNonQuery(new TranslateResult(strTemp, new DbParameter[0]),this.ProviderElement);
        }

        public override bool CreateFK(string tableName, string columName, string columType, string outterTable, string outterKey)
        {
            var strTemp = SQL_CreateRelation.Replace(TABLEARG_NAME, tableName)
                .Replace(COLUMARG_NAME, columName)
                .Replace(COLUMTYPEARG_NAME, columType)
                .Replace(OUTTERTABLE_NAME, outterTable)
                .Replace(OUTTERKEY_NAME, outterKey);
            return DataResult.CodeFirstExcuteNonQuery(new TranslateResult(strTemp, new DbParameter[0]),this.ProviderElement);
        }

        protected override string GetDbType(Type type)
        {
            if (type == typeof(Guid)) return "TEXT";
            if (type == typeof(byte[])) return "BLOB";
            var code = Type.GetTypeCode(type);
            switch (code)
            {
                case TypeCode.Boolean:
                case TypeCode.Decimal:
                case TypeCode.Byte:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                case TypeCode.SByte:
                    return "INTEGER";
                case TypeCode.Single:
                case TypeCode.Double:
                    return "REAL";
                case TypeCode.Char:
                case TypeCode.DateTime:
                case TypeCode.String:
                    return "TEXT";
                default:
                    throw new NotSupportedException("Code first 异常类型");
            }
        }

        //public override bool CreateTable(Type type)
        //{
        //    var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
        //    if (schem.ExtistTable(type.GetTableName())) return true;

        //    #region old
        //    //Dictionary<Type, Type> manyToManyCache = new Dictionary<Type, Type>();

        //    //var properties = DynamicAssignment.Instance.GetDictionary(type);
        //    //var primaryKey = properties.First(x => x.Key.EndsWith("id") && x.Value.PropertyInfo.PropertyType == typeof(long));

        //    //this.CreateTable(type.GetTableName(), primaryKey.Value.PropertyInfo.Name, this.GetDbType(primaryKey.Value.PropertyInfo.PropertyType));

        //    //foreach (var property in properties)
        //    //{
        //    //    if (property.Key == primaryKey.Key) continue;
        //    //    var propertyType = property.Value.PropertyInfo.PropertyType;

        //    //    if (Type.GetTypeCode(propertyType) != TypeCode.Object || propertyType == typeof(Guid))
        //    //    {
        //    //        this.CreateBaseType(type, property.Value.PropertyInfo);
        //    //    }
        //    //    else if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        //    //    {
        //    //        var outterType = propertyType.GetGenericArguments()[0];
        //    //        this.CreateTable(outterType);
        //    //        var outterPrimaryKey = schem.GetPrimaryKey(outterType.GetTableName());

        //    //        var relation = DynamicAssignment.Instance.GetDictionary(outterType).FirstOrDefault(x =>
        //    //            x.Value.PropertyInfo.PropertyType.IsGenericType
        //    //            && x.Value.PropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
        //    //            && x.Value.PropertyInfo.PropertyType.GetGenericArguments()[0] == type).Value;

        //    //        if (relation == null)
        //    //        {
        //    //            this.CreateFK(outterType.GetTableName(), string.Format("{0}{1}relation", outterType.GetTableName(), outterPrimaryKey),
        //    //                this.GetDbType(primaryKey.Value.PropertyInfo.PropertyType), type.GetTableName(), primaryKey.Key);
        //    //        }
        //    //        else
        //    //        {
        //    //            var fkTemp = schem.GetOuterFKey(type.GetTableName(), outterType.GetTableName());
        //    //            if (fkTemp == null)
        //    //            {
        //    //                manyToManyCache.Add(type, outterType);
        //    //                //var tableName = string.Format("{0}_{1}_Relation", type.Name, outterType.Name);
        //    //                //this.CreateTable(tableName, "id", "long");
        //    //                ////var tablePK = schem.GetPrimaryKey(tableName);
        //    //                //var firstFK = primaryKey.Value.PropertyInfo;
        //    //                //this.CreateFK(tableName, string.Format("Relation{0}{1}", type, firstFK.Name),
        //    //                //    type.GetTableName(), primaryKey.Key, null);
        //    //                //firstFK = DynamicAssignment.Instance.GetDictionary(outterType)[schem.GetPrimaryKey(outterType.GetTableName())].PropertyInfo;//[schem.GetPrimaryKey(outterType.GetTableName())];
        //    //                //this.CreateFK(tableName, string.Format("Relation{0}{1}", type, firstFK.Name),
        //    //                //    type.GetTableName(), primaryKey.Key, null);
        //    //            }
        //    //        }
        //    //    }
        //    //    else if (Type.GetTypeCode(propertyType) == TypeCode.Object && propertyType != typeof(Guid))
        //    //    {
        //    //        this.CreateTable(propertyType);
        //    //        var primary = schem.GetPrimaryKey(propertyType.GetTableName());
        //    //        this.CreateFK(propertyType.GetTableName(), string.Format("Relation{0}{1}", type.GetTableName(), primaryKey.Key), this.GetDbType(primaryKey.Value.PropertyInfo.PropertyType),
        //    //            type.GetTableName(), primaryKey.Key);
        //    //    }
        //    //}

        //    //foreach (var item in manyToManyCache)
        //    //{
        //    //    this.CreateRelation(item.Key, item.Value);
        //    //}
        //    //return true;
        //    #endregion

        //    //lock (writeLock)
        //    //{
        //        var primaryInfos = this.GetCreateBasicTableInfos(type);
        //        foreach (var item in primaryInfos)
        //        {
        //            this.CreateTable(item.Key.GetTableName(), item.Value.PropertyName, this.GetDbType(item.Value.PropertyInfo.PropertyType));
        //            var properties = DynamicAssignment.Instance.GetDictionary(item.Key);
        //            foreach (var property in properties)
        //            {
        //                if (property.Value == item.Value) continue;
        //                var typeCode = Type.GetTypeCode(property.Value.PropertyInfo.PropertyType);
        //                if (typeCode != TypeCode.Object || property.Value.PropertyInfo.PropertyType == typeof(Guid))
        //                {
        //                    this.CreateBaseType(item.Key, property.Value.PropertyInfo);
        //                }
        //            }
        //        }

        //        var relations = this.GetRelationInfos(type);
        //        foreach (var item in relations)
        //        {
        //            switch (item.RelationType)
        //            {
        //                case RelationType.One2One:
        //                case RelationType.One2Many:
        //                    this.CreateOneToOneRelation(item.BigType, item.CollectionType);
        //                    break;
        //                case RelationType.Many2Many:
        //                    this.CreateManyToManyRelation(item.BigType, item.CollectionType);
        //                    break;
        //                default:
        //                    throw new NotSupportedException("不支持的关系类型");
        //            }
        //            //this.CreateRelation(item.BigType, item.CollectionType);
        //        }
        //        return true;
        //    //}
        //}

        private Dictionary<Type,IPropertyInvoker> GetCreateBasicTableInfos(Type type)
        {
            var tables = new Dictionary<Type, IPropertyInvoker>();
            var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);
            //if (schem.ExtistTable(type.GetTableName())) return tables;

            Stack<Type> stack = new Stack<Type>();
            stack.Push(type);

            HashSet<Type> hashSet = new HashSet<Type>();

            while (stack.Count > 0)
            {
                var typeTemp = stack.Pop();
                if (hashSet.Contains(typeTemp)) continue;
                hashSet.Add(typeTemp);
                var properties = DynamicAssignment.Instance.GetDictionary(typeTemp);
                var primary = properties.FirstOrDefault(x => x.Key.EndsWith("id"));
                tables.Add(typeTemp, primary.Value);
                foreach (var property in properties)
                {
                    var propertyType = property.Value.PropertyInfo.PropertyType;
                    var typeCode = Type.GetTypeCode(propertyType);
                    if (typeCode == TypeCode.Object && propertyType != typeof(Guid))
                    {
                        if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                        {
                            var temp = propertyType.GetGenericArguments()[0];
                            stack.Push(temp);
                            //this.GetCreateBasicTableInfos(typeTemp, tables);
                        }
                        else
                        {
                            stack.Push(propertyType);
                            //this.GetCreateBasicTableInfos(propertyType, tables);
                        }
                    }
                }
            }

            //var properties = DynamicAssignment.Instance.GetDictionary(type);
            //var primary = properties.FirstOrDefault(x => x.Key.EndsWith("id") && x.Value.PropertyInfo.PropertyType == typeof(long));
            //tables.Add(type, primary.Value.PropertyInfo);
            //foreach (var property in properties)
            //{
            //    var propertyType = property.Value.PropertyInfo.PropertyType;
            //    var typeCode = Type.GetTypeCode(propertyType);
            //    if (typeCode == TypeCode.Object && propertyType != typeof(Guid))
            //    {
            //        if (propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
            //        {
            //            var typeTemp = propertyType.GetGenericArguments()[0];
            //            //this.GetCreateBasicTableInfos(typeTemp, tables);
            //        }
            //        else
            //        {
            //            //this.GetCreateBasicTableInfos(propertyType, tables);
            //        }
            //    }
            //}
            return tables;
        }

        //private IEnumerable<RelationInfo> GetRelationInfos(Type type)
        //{
        //    var cache = new List<RelationInfo>();
        //    var hashSet = new HashSet<Type>();
        //    var stack = new Stack<Type>();
        //    stack.Push(type);
        //    while (stack.Count > 0)
        //    {
        //        var typeTemp = stack.Pop();
        //        if (hashSet.Contains(typeTemp)) continue;
        //        hashSet.Add(typeTemp);
        //        var properties = DynamicAssignment.Instance.GetDictionary(typeTemp);
        //        foreach (var property in properties)
        //        {
        //            var outterType = property.Value.PropertyInfo.PropertyType;
        //            var typeCode = Type.GetTypeCode(outterType);
        //            if (typeCode == TypeCode.Object && outterType != typeof(Guid))
        //            {
        //                if (outterType.IsGenericType && outterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        //                {
        //                    outterType = outterType.GetGenericArguments()[0];
        //                    stack.Push(outterType);
        //                    var relation = DynamicAssignment.Instance.GetDictionary(outterType).FirstOrDefault(x => x.Value.PropertyInfo.PropertyType.IsGenericType
        //                        && x.Value.PropertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
        //                        && x.Value.PropertyInfo.PropertyType.GetGenericArguments()[0] == typeTemp);
        //                    var info = new RelationInfo() { BigType = typeTemp, CollectionType = outterType };
        //                    info.RelationType = relation.Value == null ? RelationType.One2Many : RelationType.Many2Many;
        //                    cache.Add(info);
        //                    if (info.RelationType == RelationType.Many2Many) hashSet.Add(outterType);
        //                }
        //                else
        //                {
        //                    stack.Push(outterType);
        //                    cache.Add(new RelationInfo()
        //                        {
        //                            BigType = typeTemp,
        //                            RelationType = RelationType.One2One,
        //                            CollectionType = outterType
        //                        });
        //                }
        //            }
        //        }
        //    }

        //    return cache;
        //}

        //protected override void CreateManyToManyRealtion(Type pkTable, Type fkTable)
        //{
        //    var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);

        //    var tableName = pkTable.GetTableName();
        //    var tablePrimary = schem.GetPrimaryKey(tableName);
        //    var tablePrimaryProperty = DynamicAssignment.Instance.GetDictionary(pkTable)[tablePrimary].PropertyInfo;

        //    var outterTableName = fkTable.GetTableName();
        //    var outterPrimary = schem.GetPrimaryKey(outterTableName);
        //    var outterPrimaryProperty = DynamicAssignment.Instance.GetDictionary(fkTable)[outterPrimary].PropertyInfo;

        //    var tableNameTemp = string.Format("{0}_{1}_Relation", tableName, outterTableName);
        //    this.CreateTable(tableNameTemp, "id", "INTEGER", true);

        //    this.CreateFK(tableNameTemp, string.Format("{0}Id", tableName), this.GetDbType(tablePrimaryProperty.PropertyType), tableName, tablePrimary);
        //    this.CreateFK(tableNameTemp, string.Format("{0}Id", outterTableName), this.GetDbType(outterPrimaryProperty.PropertyType), outterTableName, outterPrimary);
        //}

        //protected override void CreateManyToManyRelation(Type table, Type outterTable)
        //{
            
        //}

        //protected override void CreateOneToOneRealtion(Type pkTable, Type fkTable)
        //{
        //    var schem = Config.Instance.GetDbSchemInfo(this.ProviderElement);

        //    var tableName = pkTable.GetTableName();
        //    var tablePrimary = schem.GetPrimaryKey(tableName);
        //    var tablePrimaryProperty = DynamicAssignment.Instance.GetDictionary(pkTable)[tablePrimary].PropertyInfo;

        //    var outterTableName = fkTable.GetTableName();
        //    var outterPrimary = schem.GetPrimaryKey(outterTableName);
        //    var outterPrimaryProperty = DynamicAssignment.Instance.GetDictionary(fkTable)[outterPrimary].PropertyInfo;

        //    var tableNameTemp = string.Format("{0}_{1}_Relation", tableName, outterTableName);

        //    this.CreateFK(outterTableName, string.Format("{0}Relation", tableName), this.GetDbType(tablePrimaryProperty.PropertyType), tableName, tablePrimary);
        //}
        //protected override void CreateOneToOneRelation(Type table, Type outterTable)
        //{
            
        //}

        //private class RelationInfo
        //{
        //    public Type BigType { get; set; }
        //    public Type CollectionType { get; set; }
        //    public RelationType RelationType { get; set; }
        //}

        //private enum RelationType { One2One, One2Many, Many2Many }
    }
}
